﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CopyDataExtension.cs" company="">
//   
// </copyright>
// <summary>
//   The copy data extension.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Libium.Core
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using Libium.Utilities;

    /// <summary>
    ///   The copy data extension.
    /// </summary>
    public static class CopyDataExtension
    {
        #region Public Methods and Operators

        /// <summary>
        /// The get file.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="IFile"/> . 
        /// </returns>
        public static IFile GetFile(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            var files = GetFiles(data, action);
            return files == null ? null : files[0];
        }

        // TODO Refactor to async
        /// <summary>
        /// The get file async.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="Task"/> . 
        /// </returns>
        public static Task<IFile> GetFileAsync(
            this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            return Task.Factory.StartNew(() => GetFile(data, action));
        }

        /// <summary>
        /// The get file path.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string"/> . 
        /// </returns>
        /// <exception cref="NotExistsException">
        /// </exception>
        public static string GetFilePath(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            var pathes = GetFilePathes(data, NotExistsAction.ReturnNull);

            if (pathes != null && pathes.Length > 0)
            {
                return pathes[0];
            }

            if (action == NotExistsAction.ThrowException)
            {
                throw new NotExistsException("File path doesn't exist in the pasted data");
            }

            return null;
        }

        /// <summary>
        /// The get file pathes.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string[]"/> . 
        /// </returns>
        public static string[] GetFilePathes(
            this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            return data.GetValue<string[]>(DataFormats.FileDrop, action);
        }

        /// <summary>
        /// The get files.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="IList"/> . 
        /// </returns>
        /// <exception cref="NotExistsException">
        /// </exception>
        public static IList<IFile> GetFiles(
            this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            var files = new List<IFile>();

            var pathes = data.GetFilePathes(NotExistsAction.ReturnNull);
            if (pathes != null)
            {
                foreach (var path in pathes)
                {
                    files.Add(new LocalFile(path));
                }

                return files;
            }

            var url = data.GetUrl(NotExistsAction.ReturnNull);
            if (url != null)
            {
                files.Add(new UriFile(url));
                return files;
            }

            var text = data.GetText(NotExistsAction.ReturnNull);
            if (text != null)
            {
                var path = StringUtility.ExtractFilePath(text, NotExistsAction.ReturnNull);
                if (path != null && File.Exists(path))
                {
                    files.Add(new LocalFile(path));
                    return files;
                }

                url = StringUtility.ExtractUrl(text, NotExistsAction.ReturnNull);
                if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
                {
                    files.Add(new UriFile(url));
                    return files;
                }
            }

            if (action == NotExistsAction.ThrowException)
            {
                throw new NotExistsException("File doesn't exist in the pasted data");
            }

            return null;
        }

        /// <summary>
        /// The get html.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string"/> . 
        /// </returns>
        /// <exception cref="NotExistsException">
        /// </exception>
        public static string GetHtml(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            var html = data.GetValue<string>(DataFormats.Html, NotExistsAction.ReturnNull);
            if (html == null)
            {
                html = data.GetValue<string>(DataFormats.TextHtml, NotExistsAction.ReturnNull);
            }

            if (html == null && action == NotExistsAction.ThrowException)
            {
                throw new NotExistsException("Html doesn't exist in the pasted data");
            }

            return html;
        }

        /// <summary>
        /// The get rich text.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string"/> . 
        /// </returns>
        public static string GetRichText(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            return data.GetValue<string>(DataFormats.RichTextFormat, action);
        }

        /// <summary>
        /// The get text.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string"/> . 
        /// </returns>
        /// <exception cref="NotExistsException">
        /// </exception>
        public static string GetText(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            var text = data.GetValue<string>(DataFormats.SystemString, NotExistsAction.ReturnNull);
            if (text == null)
            {
                text = data.GetValue<string>(DataFormats.UnicodeText, NotExistsAction.ReturnNull);
            }

            if (text == null)
            {
                text = data.GetValue<string>(DataFormats.OemText, NotExistsAction.ReturnNull);
            }

            if (text == null && action == NotExistsAction.ThrowException)
            {
                throw new NotExistsException("Text doesn't exist in the pasted data");
            }

            return text;
        }

        /// <summary>
        /// The get url.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <returns>
        /// The <see cref="string"/> . 
        /// </returns>
        /// <exception cref="NotExistsException">
        /// </exception>
        public static string GetUrl(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            string url = null;

            var stream = data.GetValue<Stream>(DataFormats.UniformResourceLocator, NotExistsAction.ReturnNull);
            if (stream != null)
            {
                url = Encoding.ASCII.GetString(stream.CopyToArray());
            }

            if (url == null)
            {
                stream = data.GetValue<Stream>(DataFormats.UniformResourceLocatorW, NotExistsAction.ReturnNull);
                if (stream != null)
                {
                    url = Encoding.Unicode.GetString(stream.CopyToArray());
                }
            }

            if (url == null)
            {
                if (action == NotExistsAction.ThrowException)
                {
                    throw new NotExistsException("Url doesn't exist in the pasted data");
                }
            }
            else
            {
                url = url.SubstringBefore('\n').TrimEnd('\0');
            }

            return url;
        }

        /// <summary>
        /// The get value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="action">
        /// The action. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/> . 
        /// </returns>
        public static T GetValue<T>(this ICopyData data, NotExistsAction action = NotExistsAction.ThrowException)
        {
            return data.GetValue<T>(typeof(T).FullName, action);
        }

        /// <summary>
        /// The is exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsExist<T>(this ICopyData data)
        {
            return data.IsExist(typeof(T).FullName);
        }

        /// <summary>
        /// The is file exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="mimeTypes">
        /// The mime types. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsFileExist(this ICopyData data, IEnumerable<MimeType> mimeTypes = null)
        {
            return data.IsFilesExist(mimeTypes);
        }

        /// <summary>
        /// The is file path exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="mimeTypes">
        /// The mime types. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsFilePathExist(this ICopyData data, IEnumerable<MimeType> mimeTypes = null)
        {
            return IsFilePathesExist(data, mimeTypes);
        }

        /// <summary>
        /// The is file pathes exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="mimeTypes">
        /// The mime types. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsFilePathesExist(this ICopyData data, IEnumerable<MimeType> mimeTypes = null)
        {
            var isExist = data.IsExist(DataFormats.FileDrop);
            if (isExist && mimeTypes != null)
            {
                var filePathes = GetFilePathes(data, NotExistsAction.ReturnNull);
                foreach (var filePath in filePathes)
                {
                    var extension = PathUtility.GetExtensionWithoutDot(filePath);
                    var isValid = false;
                    foreach (var mimeType in mimeTypes)
                    {
                        if (mimeType.FileExtensions.Contains(extension))
                        {
                            isValid = true;
                            break;
                        }
                    }

                    if (!isValid)
                    {
                        return false;
                    }
                }
            }

            return isExist;
        }

        /// <summary>
        /// The is files exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="mimeTypes">
        /// The mime types. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsFilesExist(this ICopyData data, IEnumerable<MimeType> mimeTypes = null)
        {
            return data.IsFilePathesExist(mimeTypes) || data.IsUrlExist() || data.IsTextExist();
        }

        /// <summary>
        /// The is html exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsHtmlExist(this ICopyData data)
        {
            return data.IsExist(DataFormats.Html) || data.IsExist(DataFormats.TextHtml);
        }

        /// <summary>
        /// The is rich text exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsRichTextExist(this ICopyData data)
        {
            return data.IsExist(DataFormats.RichTextFormat);
        }

        /// <summary>
        /// The is text exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsTextExist(this ICopyData data)
        {
            return data.IsExist(DataFormats.SystemString) || data.IsExist(DataFormats.UnicodeText)
                   || data.IsExist(DataFormats.OemText);
        }

        /// <summary>
        /// The is url exist.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <returns>
        /// The <see cref="bool"/> . 
        /// </returns>
        public static bool IsUrlExist(this ICopyData data)
        {
            return data.IsExist(DataFormats.UniformResourceLocator) || data.IsExist(DataFormats.UniformResourceLocatorW);
        }

        /// <summary>
        /// The set file.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        public static void SetFile(this ICopyData data, Func<IFile> func)
        {
            data.SetFilePath(() => func().GetPath());
            data.SetText(() => func().GetPath());
        }

        /// <summary>
        /// The set file.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="file">
        /// The file. 
        /// </param>
        public static void SetFile(this ICopyData data, IFile file)
        {
            SetFile(data, () => file);
        }

        /// <summary>
        /// The set file path.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        public static void SetFilePath(this ICopyData data, Func<string> func)
        {
            SetFilePathes(data, new[] { func() });
        }

        /// <summary>
        /// The set file path.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="path">
        /// The path. 
        /// </param>
        public static void SetFilePath(this ICopyData data, string path)
        {
            SetFilePath(data, () => path);
        }

        /// <summary>
        /// The set file pathes.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        public static void SetFilePathes(this ICopyData data, Func<IEnumerable<string>> func)
        {
            Contract.Requires(func != null);
            data.SetValue(DataFormats.FileDrop, func().ToArray());
        }

        /// <summary>
        /// The set file pathes.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="pathes">
        /// The pathes. 
        /// </param>
        public static void SetFilePathes(this ICopyData data, IList<string> pathes)
        {
            Contract.Requires(pathes != null && pathes.Count > 0);
            SetFilePathes(data, () => pathes);
        }

        /// <summary>
        /// The set file pathes.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="pathes">
        /// The pathes. 
        /// </param>
        public static void SetFilePathes(this ICopyData data, params string[] pathes)
        {
            SetFilePathes(data, (IList<string>)pathes);
        }

        /// <summary>
        /// The set html.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="html">
        /// The html. 
        /// </param>
        public static void SetHtml(this ICopyData data, string html)
        {
            data.SetValue(DataFormats.Html, html);
            data.SetValue(DataFormats.TextHtml, html);
        }

        /// <summary>
        /// The set rich text.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="text">
        /// The text. 
        /// </param>
        public static void SetRichText(this ICopyData data, string text)
        {
            data.SetValue(DataFormats.RichTextFormat, text);
        }

        /// <summary>
        /// The set text.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        public static void SetText(this ICopyData data, Func<string> func)
        {
            data.SetValue(DataFormats.SystemString, func);
            data.SetValue(DataFormats.UnicodeText, func);
            data.SetValue(DataFormats.OemText, func);
        }

        /// <summary>
        /// The set text.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="text">
        /// The text. 
        /// </param>
        public static void SetText(this ICopyData data, string text)
        {
            SetText(data, text);
        }

        /// <summary>
        /// The set url.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="url">
        /// The url. 
        /// </param>
        /// <param name="title">
        /// The title. 
        /// </param>
        public static void SetUrl(this ICopyData data, string url, string title)
        {
            // create FileGroupDescriptor stream with the title of the link
            var titleBytes = Encoding.ASCII.GetBytes(title + ".url"); // dont forget the .url!
            var fileGroupDescriptor = new byte[336];
            titleBytes.CopyTo(fileGroupDescriptor, 76);
            fileGroupDescriptor[0] = 0x1;
            fileGroupDescriptor[4] = 0x40;
            fileGroupDescriptor[5] = 0x80;
            fileGroupDescriptor[72] = 0x78; // winXP fix
            var fileGroupDescriptorStream = new MemoryStream(fileGroupDescriptor);
            data.SetValue(DataFormats.FileGroupDescriptorW, fileGroupDescriptorStream);

            var fileContents = "[InternetShortcut]" + Environment.NewLine + "URL=" + url + Environment.NewLine;
            var contentsBytes = Encoding.ASCII.GetBytes(fileContents);
            var contentsStream = new MemoryStream(contentsBytes);
            data.SetValue(DataFormats.FileContents, contentsStream);

            var urlBytes = Encoding.ASCII.GetBytes(url);
            var urlStream = new MemoryStream(urlBytes);
            data.SetValue(DataFormats.UniformResourceLocator, urlStream);

            urlBytes = Encoding.Unicode.GetBytes(url);
            urlStream = new MemoryStream(urlBytes);
            data.SetValue(DataFormats.UniformResourceLocatorW, urlStream);
        }

        /// <summary>
        /// The set value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="value">
        /// The value. 
        /// </param>
        public static void SetValue(this ICopyData data, ICopyValue value)
        {
            data.SetValue(value.ValueType.FullName, value);
        }

        /// <summary>
        /// The set value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="format">
        /// The format. 
        /// </param>
        /// <param name="value">
        /// The value. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        public static void SetValue<T>(this ICopyData data, string format, T value)
        {
            data.SetValue(format, new CopyValue<T>(value));
        }

        /// <summary>
        /// The set value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="value">
        /// The value. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        public static void SetValue<T>(this ICopyData data, T value)
        {
            SetValue(data, typeof(T).FullName, value);
        }

        /// <summary>
        /// The set value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="format">
        /// The format. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        public static void SetValue<T>(this ICopyData data, string format, Func<T> func)
        {
            data.SetValue(format, new FuncValue<T>(func));
        }

        /// <summary>
        /// The set value.
        /// </summary>
        /// <param name="data">
        /// The data. 
        /// </param>
        /// <param name="func">
        /// The func. 
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        public static void SetValue<T>(this ICopyData data, Func<T> func)
        {
            SetValue(data, typeof(T).FullName, func);
        }

        #endregion
    }
}